/**
 * Created by tolya-yanot
 * Creation date:  10.05.13 20:25
 */
package {
import com.adobe.utils.AGALMiniAssembler;
import com.adobe.utils.PerspectiveMatrix3D;

import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.display.Stage3D;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.display3D.Context3D;
import flash.display3D.Context3DProgramType;
import flash.display3D.Context3DTextureFormat;
import flash.display3D.Context3DVertexBufferFormat;
import flash.display3D.IndexBuffer3D;
import flash.display3D.Program3D;
import flash.display3D.VertexBuffer3D;
import flash.display3D.textures.Texture;
import flash.events.Event;
import flash.geom.Matrix;
import flash.geom.Matrix3D;
import flash.geom.Vector3D;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;
import flash.utils.getTimer;

[SWF(width="640", height="480", frameRate="60")]
public class Main2 extends Sprite {

    private var fpsLast:uint = getTimer();
    private var fpsTicks:uint = 0;
    private var fpsTf:TextField;

    private const swfWidth:int = 640;
    private const swfHeight:int = 480;
    private const textureSize:int = 512;

    private var context3D:Context3D;
    private var shaderProgram1:Program3D;
    private var shaderProgram2:Program3D;
    private var shaderProgram3:Program3D;
    private var shaderProgram4:Program3D;

    private var vertexBuffer:VertexBuffer3D;
    private var indexBuffer:IndexBuffer3D;
    private var meshVertexData:Vector.<Number>;
    private var meshIndexData:Vector.<uint>;


    private var projectionMatrix:PerspectiveMatrix3D = new PerspectiveMatrix3D();
    private var modelMatrix:Matrix3D = new Matrix3D();
    private var viewMatrix:Matrix3D = new Matrix3D();
    private var modelViewProjection:Matrix3D = new Matrix3D();
    private var t:Number = 0;
    private var looptemp:int = 0;

    [Embed(source="/texture.jpg")]
    private var myTextureBitmap:Class;
    private var myTextureData:Bitmap = new myTextureBitmap;
    private var myTexture:Texture;

    public function Main2() {
        if (stage) {
            init();
        } else {
            addEventListener(Event.ADDED_TO_STAGE, init)
        }
    }

    private function init(event:Event = null):void {
        removeEventListener(Event.ADDED_TO_STAGE, init);

        stage.scaleMode = StageScaleMode.NO_SCALE;
        stage.align = StageAlign.TOP_LEFT;

        initGUI();

        stage.stage3Ds[0].addEventListener(Event.CONTEXT3D_CREATE, onContext3DCreate);
        stage.stage3Ds[0].requestContext3D();
    }

    private function initGUI():void {
        var myFormat:TextFormat = new TextFormat();
        myFormat.color = 0xffffff;
        myFormat.size = 13;

        fpsTf = new TextField();
        fpsTf.x = 0;
        fpsTf.y = 0;
        fpsTf.selectable = false;
        fpsTf.autoSize = TextFieldAutoSize.LEFT;
        fpsTf.defaultTextFormat = myFormat;
        fpsTf.text = "Initializing Stage3d";
        addChild(fpsTf);

        var label1:TextField = new TextField();
        label1.x = 100;
        label1.y = 180;
        label1.selectable = false;
        label1.autoSize = TextFieldAutoSize.LEFT;
        label1.defaultTextFormat = myFormat;
        label1.text = "Shader 1: Textured";
        addChild(label1);

        var label2:TextField = new TextField();
        label2.x = 400;
        label2.y = 180;
        label2.selectable = false;
        label2.autoSize = TextFieldAutoSize.LEFT;
        label2.defaultTextFormat = myFormat;
        label2.text = "Shader 2: Vertex RGB";
        addChild(label2);

        var label3:TextField = new TextField();
        label3.x = 80;
        label3.y = 440;
        label3.selectable = false;
        label3.autoSize = TextFieldAutoSize.LEFT;
        label3.defaultTextFormat = myFormat;
        label3.text = "Shader 3: Vertex RGB + Textured";
        addChild(label3);

        var label4:TextField = new TextField();
        label4.x = 340;
        label4.y = 440;
        label4.selectable = false;
        label4.autoSize = TextFieldAutoSize.LEFT;
        label4.defaultTextFormat = myFormat;
        label4.text = "Shader 4 Textured + setProgramConstants";
        addChild(label4);
    }

    private function onContext3DCreate(event:Event):void {
        removeEventListener(Event.ENTER_FRAME, onEnterFrame);
        context3D = Stage3D(event.target).context3D;
        context3D.enableErrorChecking = false;
        initData();
        context3D.configureBackBuffer(swfWidth, swfHeight, 0, true);
        initShaders();

        indexBuffer = context3D.createIndexBuffer(meshIndexData.length);
        indexBuffer.uploadFromVector(meshIndexData, 0, meshIndexData.length);

        vertexBuffer = context3D.createVertexBuffer(meshVertexData.length / 12, 12);
        vertexBuffer.uploadFromVector(meshVertexData, 0, meshVertexData.length / 12);

        myTexture = context3D.createTexture(textureSize, textureSize, Context3DTextureFormat.BGRA, false);
        var ws:int = myTextureData.bitmapData.width;
        var hs:int = myTextureData.bitmapData.height;
        var level:int = 0;
        var tmp:BitmapData;
        var transform:Matrix = new Matrix();
        tmp = new BitmapData(ws, hs, true, 0);
        while (ws >= 1 && hs >= 1) {
            tmp.draw(myTextureData.bitmapData, transform, null, null, null, true);
            myTexture.uploadFromBitmapData(tmp, level);
            transform.scale(0.5, 0.5);
            level++;
            ws >>= 1;
            hs >>= 1;
            if (hs && ws) {
                tmp.dispose();
                tmp = new BitmapData(ws, hs, true, 0);
            }
        }
        tmp.dispose();

        projectionMatrix.identity();
        projectionMatrix.perspectiveFieldOfViewRH(45, swfWidth / swfHeight, 0.01, 100);
        viewMatrix.identity();
        viewMatrix.appendTranslation(0, 0, -10);
        addEventListener(Event.ENTER_FRAME, onEnterFrame)
    }

    private function initShaders():void {
        var vertexShaderAssembler:AGALMiniAssembler = new AGALMiniAssembler();
        var vertexShader:String = "m44 op, va0, vc0\n" +
                "mov v0, va0\n" +    // copy xyz
                "mov v1, va1\n" + // copy uv
                "mov v2, va2\n"; // copy rgba

        vertexShaderAssembler.assemble(Context3DProgramType.VERTEX, vertexShader);

        // fs 1

        var fragmentShader1:String = "tex ft0, v1, fs0 <2d,repeat,miplinear>\n" +
                "mov oc, ft0\n";

        var fragmentShaderAssembler1:AGALMiniAssembler = new AGALMiniAssembler();
        fragmentShaderAssembler1.assemble(Context3DProgramType.FRAGMENT, fragmentShader1);
        shaderProgram1 = context3D.createProgram();
        shaderProgram1.upload(vertexShaderAssembler.agalcode, fragmentShaderAssembler1.agalcode);

        // fs 2

        var fragmentShader2:String = "mov oc, v2\n";

        var fragmentShaderAssembler2:AGALMiniAssembler = new AGALMiniAssembler();
        fragmentShaderAssembler2.assemble(Context3DProgramType.FRAGMENT, fragmentShader2);
        shaderProgram2 = context3D.createProgram();
        shaderProgram2.upload(vertexShaderAssembler.agalcode, fragmentShaderAssembler2.agalcode);

        // fs 3

        var fragmentShader3:String = "tex ft0, v1, fs0 <2d,repeat,miplinear>\n" +
                "mul ft1, v2, ft0\n" +
                "mov oc, ft1\n";

        var fragmentShaderAssembler3:AGALMiniAssembler = new AGALMiniAssembler();
        fragmentShaderAssembler3.assemble(Context3DProgramType.FRAGMENT, fragmentShader3);
        shaderProgram3 = context3D.createProgram();
        shaderProgram3.upload(vertexShaderAssembler.agalcode, fragmentShaderAssembler3.agalcode);

        // fs 4

        var fragmentShader4:String = "tex ft0, v1, fs0 <2d,repeat,miplinear>\n" +
                "mul ft1, fc0, ft0\n" +
                "mov oc, ft1\n";

        var fragmentShaderAssembler4:AGALMiniAssembler = new AGALMiniAssembler();
        fragmentShaderAssembler4.assemble(Context3DProgramType.FRAGMENT, fragmentShader4);
        shaderProgram4 = context3D.createProgram();
        shaderProgram4.upload(vertexShaderAssembler.agalcode, fragmentShaderAssembler4.agalcode);
    }

    private function initData():void {
        meshIndexData = new <uint>[0, 1, 2, 0, 2, 3];
        meshVertexData = new <Number>[
            // x,y,z,u,v,nX,nY,nZ,r,g,b,a
            -1, -1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1,
            1, -1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1,
            1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1,
            -1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1
        ];

    }

    private function onEnterFrame(event:Event):void {
        context3D.clear(0, 0, 0);
        t += 2.0;

        for (looptemp = 0; looptemp < 4; looptemp++) {
            modelMatrix.identity();

            switch (looptemp) {
                case 0:
                    context3D.setTextureAt(0, myTexture);
                    context3D.setProgram(shaderProgram1);
                    modelMatrix.appendRotation(t * 0.7, Vector3D.Y_AXIS);
                    modelMatrix.appendRotation(t * 0.6, Vector3D.X_AXIS);
                    modelMatrix.appendRotation(t * 1, Vector3D.Y_AXIS);
                    modelMatrix.appendTranslation(-3, 3, 0);
                    break;
                case 1:
                    context3D.setTextureAt(0, null);
                    context3D.setProgram(shaderProgram2);
                    modelMatrix.appendRotation(t * -0.2, Vector3D.Y_AXIS);
                    modelMatrix.appendRotation(t * 0.4, Vector3D.X_AXIS);
                    modelMatrix.appendRotation(t * 0.7, Vector3D.Y_AXIS);
                    modelMatrix.appendTranslation(3, 3, 0);
                    break;
                case 2:
                    context3D.setTextureAt(0, myTexture);
                    context3D.setProgram(shaderProgram3);
                    modelMatrix.appendRotation(t * 1, Vector3D.Y_AXIS);
                    modelMatrix.appendRotation(t * -0.2, Vector3D.X_AXIS);
                    modelMatrix.appendRotation(t * 0.3, Vector3D.Y_AXIS);
                    modelMatrix.appendTranslation(-3, -3, 0);
                    break;
                case 3:
                    context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, new <Number>[1, Math.abs(Math.cos(t / 50)), 0, 1]);
                    context3D.setTextureAt(0, myTexture);
                    context3D.setProgram(shaderProgram4);
                    modelMatrix.appendRotation(t * 0.3, Vector3D.Y_AXIS);
                    modelMatrix.appendRotation(t * 0.3, Vector3D.X_AXIS);
                    modelMatrix.appendRotation(t * -0.3, Vector3D.Y_AXIS);
                    modelMatrix.appendTranslation(3, -3, 0);
                    break;
            }

            modelViewProjection.identity();
            modelViewProjection.append(modelMatrix);
            modelViewProjection.append(viewMatrix);
            modelViewProjection.append(projectionMatrix);

            context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, modelViewProjection, true);

            context3D.setVertexBufferAt(0, vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3);
            context3D.setVertexBufferAt(1, vertexBuffer, 3, Context3DVertexBufferFormat.FLOAT_2);
            context3D.setVertexBufferAt(2, vertexBuffer, 8, Context3DVertexBufferFormat.FLOAT_4);

            context3D.drawTriangles(indexBuffer, 0, meshIndexData.length / 3);
        }

        context3D.present();

        fpsTicks++;
        var now:uint = getTimer();
        var delta:uint = now - fpsLast;
        if (delta > 1000) {
            var fps:Number = fpsTicks / delta * 1000;
            fpsTf.text = fps.toFixed(1);
            fpsTicks = 0;
            fpsLast = now;
        }
    }


}
}
